package io.pisceshub.muchat.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.pisceshub.muchat.common.core.contant.AppConst;
import io.pisceshub.muchat.common.core.enums.CommonEnums;
import io.pisceshub.muchat.common.core.enums.MessageStatus;
import io.pisceshub.muchat.common.core.enums.MessageType;
import io.pisceshub.muchat.common.core.enums.ResultCode;
import io.pisceshub.muchat.common.core.model.GroupMessageInfo;
import io.pisceshub.muchat.sdk.IMClient;
import io.pisceshub.muchat.server.adapter.SensitiveWordAdapter;
import io.pisceshub.muchat.server.common.contant.RedisKey;
import io.pisceshub.muchat.server.common.entity.Group;
import io.pisceshub.muchat.server.common.entity.GroupMember;
import io.pisceshub.muchat.server.common.entity.GroupMessage;
import io.pisceshub.muchat.server.common.enums.GroupEnum;
import io.pisceshub.muchat.server.common.vo.message.GroupMessageSendReq;
import io.pisceshub.muchat.server.common.vo.message.MessageSendResp;
import io.pisceshub.muchat.server.common.vo.user.GroupVO;
import io.pisceshub.muchat.server.exception.GlobalException;
import io.pisceshub.muchat.server.mapper.GroupMessageMapper;
import io.pisceshub.muchat.server.service.IGroupMemberService;
import io.pisceshub.muchat.server.service.IGroupMessageService;
import io.pisceshub.muchat.server.service.IGroupService;
import io.pisceshub.muchat.server.util.BeanUtils;
import io.pisceshub.muchat.server.util.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class GroupMessageServiceImpl extends
    ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService {

  @Autowired
  private IGroupService groupService;

  @Autowired
  private IGroupMemberService groupMemberService;

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  @Autowired
  private IMClient imClient;

  @Autowired
  private GroupMessageMapper groupMessageMapper;

  /**
   * 默认一次查询多少条消息
   */
  private final static Integer defaultQueryMessageCount = 15;

  @Autowired
  private SensitiveWordAdapter sensitiveWordAdapter;

  /**
   * 发送群聊消息(与mysql所有交换都要进行缓存)
   *
   * @param vo
   * @return 群聊id
   */
  @Override
  public MessageSendResp sendMessage(GroupMessageSendReq vo) {
    Long userId = SessionContext.getSession().getId();
    Group group = groupService.getById(vo.getGroupId());
    if (group == null) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
    }
    if (CommonEnums.Yes.getCode().equals(group.getDeleted())) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊已解散");
    }
    // 判断是否在群里
    List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
    if (!userIds.contains(userId)) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面，无法发送消息");
    }

    String replaced = sensitiveWordAdapter.replace(vo.getContent());
    if (replaced.matches("^\\*+$")) {
      throw new GlobalException("不允许发送该消息内容");
    }
    vo.setContent(replaced);

    // 保存消息
    GroupMessage msg = BeanUtils.copyProperties(vo, GroupMessage.class);
    msg.setSendId(userId);
    msg.setSendTime(new Date());
    this.save(msg);
    // 不用发给自己
    userIds = userIds.stream().filter(id -> !userId.equals(id)).collect(Collectors.toList());
    // 群发userIds = {ArrayList@14535}  size = 174
    GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
    imClient.sendGroupMessage(userIds, msgInfo);
    log.info("发送群聊消息，发送id:{},群聊id:{},内容:{}", userId, vo.getGroupId(), vo.getContent());

    return MessageSendResp.builder().id(msg.getId()).content(msg.getContent()).build();
  }

  /**
   * 撤回消息
   *
   * @param id 消息id
   */
  @Override
  public void recallMessage(Long id) {
    Long userId = SessionContext.getSession().getId();
    GroupMessage msg = this.getById(id);
    if (msg == null) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
    }
    if (msg.getSendId() != userId) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回");
    }
    if (System.currentTimeMillis() - msg.getSendTime().getTime()
        > AppConst.ALLOW_RECALL_SECOND * 1000) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟，无法撤回");
    }
    // 判断是否在群里
    GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), userId);
    if (member == null) {
      throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面，无法撤回消息");
    }
    // 修改数据库
    msg.setStatus(MessageStatus.RECALL.code());
    this.updateById(msg);
    // 群发
    List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId());
    // 不用发给自己
    userIds = userIds.stream().filter(uid -> !userId.equals(uid)).collect(Collectors.toList());
    GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
    msgInfo.setType(MessageType.TIP.code());
    String content = String.format("'%s'撤回了一条消息", member.getAliasName());
    msgInfo.setContent(content);
    msgInfo.setSendTime(new Date());
    imClient.sendGroupMessage(userIds, msgInfo);
    log.info("撤回群聊消息，发送id:{},群聊id:{},内容:{}", userId, msg.getGroupId(),
        msg.getContent());
  }

  /**
   * 异步拉取群聊消息，通过websocket异步推送
   *
   * @return
   */
  @Override
  public void pullUnreadMessage() {
    Long userId = SessionContext.getSession().getId();
    List<Long> recvIds = new LinkedList();
    recvIds.add(userId);
    List<GroupMember> members = groupMemberService.findByUserId(userId);
    for (GroupMember member : members) {
      // 获取群聊已读的最大消息id，只推送未读消息
      String key = RedisKey.IM_GROUP_READED_POSITION + member.getGroupId() + ":" + userId;
      Integer maxReadedId = (Integer) redisTemplate.opsForValue().get(key);
      QueryWrapper<GroupMessage> wrapper = new QueryWrapper();
      wrapper.lambda()
          .eq(GroupMessage::getGroupId, member.getGroupId())
          .gt(GroupMessage::getSendTime, member.getCreatedTime())
          .ne(GroupMessage::getSendId, userId)
          .ne(GroupMessage::getStatus, MessageStatus.RECALL.code());
      if (maxReadedId != null) {
        wrapper.lambda().gt(GroupMessage::getId, maxReadedId);
      }
      wrapper.last("limit 100");
      List<GroupMessage> messages = this.list(wrapper);
      if (messages.isEmpty()) {
        continue;
      }
      // 组装消息，准备推送
      List<GroupMessageInfo> messageInfos = messages.stream().map(m -> {
        GroupMessageInfo msgInfo = BeanUtils.copyProperties(m, GroupMessageInfo.class);
        return msgInfo;
      }).collect(Collectors.toList());
      // 发送消息
      GroupMessageInfo[] infoArr = messageInfos.toArray(new GroupMessageInfo[messageInfos.size()]);
      imClient.sendGroupMessage(Collections.singletonList(userId), infoArr);
      log.info("拉取未读群聊消息，用户id:{},群聊id:{},数量:{}", userId, member.getGroupId(),
          messageInfos.size());
    }

  }

  /**
   * 拉取历史聊天记录
   *
   * @param groupId 群聊id
   * @return 聊天记录列表
   */
  @Override
  public List<GroupMessageInfo> findHistoryMessage(Long groupId, Long lastMessageId) {
    GroupVO groupVO = groupService.findById(groupId);
    if (groupVO == null) {
      throw new GlobalException("群聊不存在");
    }
    Date beforeDate = null;
    if (GroupEnum.GroupType.Plain.getCode().equals(groupVO.getGroupType())) {
      Long userId = SessionContext.getSession().getId();
      // 群聊成员信息
      GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId);
      if (member == null || member.getQuit()) {
        throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊中");
      }
      // 查询聊天记录，只查询加入群聊时间之后的消息
      beforeDate = member.getCreatedTime();
    }
    List<GroupMessage> messages = groupMessageMapper
        .findHistoryMessage(groupId, beforeDate, lastMessageId, defaultQueryMessageCount);
    List<GroupMessageInfo> messageInfos = messages.stream().map(m -> {
      GroupMessageInfo groupMessageInfo = BeanUtil.copyProperties(m, GroupMessageInfo.class);
      return groupMessageInfo;
    }).collect(Collectors.toList());
    return messageInfos;
  }

}
